{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 00. Quick Start"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Imports\n",
    "\n",
    "the fdtd library is simply imported as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import fdtd"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setting the backend"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "the fdtd library allows for setting a backend. There exist a Numpy backend and several PyTorch backends. The available backends are:\n",
    "- `\"numpy\"` (defaults to float64 arrays)\n",
    "- `\"torch\"` (defaults to float64 tensors)\n",
    "- `\"torch.float32\"`\n",
    "- `\"torch.float64\"`\n",
    "- `\"torch.cuda\"` (defaults to float64 tensors)\n",
    "- `\"torch.cuda.float32\"`\n",
    "- `\"torch.cuda.float64\"`\n",
    "\n",
    "In general, the `\"numpy\"` backend is preferred for standard CPU calculations with `\"float64\"` precision. In general, `\"float64\"` precision is always preferred for FDTD simulations, however, `\"float32\"` might give a significant performance boost.\n",
    "\n",
    "The `\"cuda\"` backends are only available for computers with a GPU."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fdtd.set_backend(\"numpy\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The FDTD-grid"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The FDTD grid defines the simulation region. \n",
    "```python\n",
    "# signature\n",
    "fdtd.Grid(\n",
    "    shape: Tuple[Number, Number, Number],\n",
    "    grid_spacing: float = 155e-9,\n",
    "    permittivity: float = 1.0,\n",
    "    permeability: float = 1.0,\n",
    "    courant_number: float = None,\n",
    ")\n",
    "```\n",
    "\n",
    "A grid is defined by its `shape`, which is just a 3D tuple of `Number`-types (integers or floats). If the shape is given in floats, it denotes the width, height and length of the grid in meters. If the shape is given in integers, it denotes the width, height and length of the grid in terms of the `grid_spacing`. Internally, these numbers will be translated to three integers: `grid.Nx`, `grid.Ny` and `grid.Nz`.\n",
    "\n",
    "A `grid_spacing` can be given. For stability reasons, it is recommended to choose a grid spacing that is at least 10 times smaller than the *smallest* wavelength in the grid. This means that for a grid containing a source with wavelength `1550nm` and a material with refractive index of `3.1`, the recommended minimum `grid_spacing` turns out to be `50pm`\n",
    "\n",
    "For the `permittivity` and `permeability` floats or arrays with the following shapes\n",
    "\n",
    "- `(grid.Nx, grid.Ny, grid.Nz)`\n",
    "- or `(grid.Nx, grid.Ny, grid.Nz, 1)`\n",
    "- or `(grid.Nx, grid.Ny, grid.Nz, 3)`\n",
    "\n",
    "are expected. In the last case, the shape implies the possibility for different permittivity for each of the major axes (so-called *uniaxial* or *biaxial* materials). \n",
    "Internally, these variables will be converted (for performance reasons) to their inverses `grid.inverse_permittivity` array and a `grid.inverse_permeability` array of shape `(grid.Nx, grid.Ny, grid.Nz, 3)`. It is possible to change those arrays after making the grid.\n",
    "\n",
    "Finally, the `courant_number` of the grid determines the relation between the `time_step` of the simulation and the `grid_spacing` of the grid. If not given, it is chosen to be the maximum number allowed by the [Courant-Friedrichs-Lewy Condition](https://en.wikipedia.org/wiki/Courant–Friedrichs–Lewy_condition): `1` for `1D` simulations, `1/√2` for `2D` simulations and `1/√3` for `3D` simulations (the dimensionality will be derived by the shape of the grid). For stability reasons, it is recommended not to change this value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid = fdtd.Grid(\n",
    "    shape = (25e-6, 15e-6, 1), # 25um x 15um x 1 (grid_spacing) --> 2D FDTD\n",
    ")\n",
    "\n",
    "print(grid)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adding an object to the grid"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "An other option to locally change the `permittivity` or `permeability` in the grid is to add an `Object` to the grid.\n",
    "```python\n",
    "# signature\n",
    "fdtd.Object(\n",
    "    permittivity: Tensorlike,\n",
    "    name: str = None\n",
    ")\n",
    "```\n",
    "An object defines a part of the grid with modified update equations, allowing to introduce for example absorbing materials or biaxial materials for which mixing between the axes are present through `Pockels coefficients` or many more. In this case we'll make an object with a different `permittivity` than the grid it is in.\n",
    "\n",
    "Just like for the grid, the `Object` expects a `permittivity` to be a floats or an array of the following possible shapes\n",
    "\n",
    "- `(obj.Nx, obj.Ny, obj.Nz)`\n",
    "- or `(obj.Nx, obj.Ny, obj.Nz, 1)`\n",
    "- or `(obj.Nx, obj.Ny, obj.Nz, 3)`\n",
    "\n",
    "Note that the values `obj.Nx`, `obj.Ny` and `obj.Nz` are not given to the object constructor. They are in stead derived from its placing in the grid:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid[11:32, 30:84, 0] = fdtd.Object(permittivity=1.7**2, name=\"object\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Several things happen here. First of all, the object is given the space `[11:32, 30:84, 0]` in the grid. Because it is given this space, the object's `Nx`, `Ny` and `Nz` are automatically set. Furthermore, by supplying a name to the object, this name will become available in the grid:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(grid.object)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can add a second object to the grid:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid[13e-6:18e-6, 5e-6:8e-6, 0] = fdtd.Object(permittivity=1.5**2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we chose to slice the grid with floating point numbers, which will be replaced by integer `Nx`, `Ny` and `Nz` during the registration of the object. Since we didnt give the object a name, the object won't be available to us as an attribute of the grid. However, it is still available to us via the `grid.objects` list:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(grid.objects)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This list stores all objects (i.e. of type `fdtd.Object`) in the order that they were added to the grid."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adding a source to the grid"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Similarly as to adding an object to the grid, an `fdtd.LineSource` can also be added:\n",
    "```python\n",
    "# signature\n",
    "fdtd.LineSource(\n",
    "    period: Number = 15, # timesteps or seconds\n",
    "    amplitude: float = 1.0,\n",
    "    phase_shift: float = 0.0,\n",
    "    name: str = None,\n",
    ")\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Similarly to an `fdtd.Object`, an `fdtd.Source` size is defined by its placement on the grid:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid[7.5e-6:8.0e-6, 11.8e-6:13.0e-6, 0] = fdtd.LineSource(\n",
    "    period = 1550e-9 / (3e8), name=\"source\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, it is important to note that in this case we are adding a `LineSource`, i.e. the source spans the diagonal of the cube defined by the slices. Internally, these slices will be converted into lists to ensure the expected behavior:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(grid.source)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that one could have also supplied lists to index the grid in the first place. This feature could be useful to create a `LineSource` of arbitrary shape."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adding a detector to the grid"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Adding a detector to the grid works the same as adding a source\n",
    "```python\n",
    "# signature\n",
    "fdtd.LineDetector(\n",
    "    name=None\n",
    ")\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid[12e-6, :, 0] = fdtd.LineDetector(name=\"detector\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(grid.detector)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adding grid boundaries"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Although, having an object, source and detector to simulate is in principle enough to perform an FDTD simulation, One also needs to define a grid boundary to prevent the fields to be reflected. One of those boundaries that can be added to the grid is a [Perfectly Matched Layer](https://en.wikipedia.org/wiki/Perfectly_matched_layer) or `PML`. These are basically absorbing boundaries.\n",
    "\n",
    "```python\n",
    "fdtd.PML(\n",
    "    a: float = 1e-8, # stability factor\n",
    "    name: str = None\n",
    ")\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# x boundaries\n",
    "# grid[0, :, :] = fdtd.PeriodicBoundary(name=\"xbounds\")\n",
    "grid[0:10, :, :] = fdtd.PML(name=\"pml_xlow\")\n",
    "grid[-10:, :, :] = fdtd.PML(name=\"pml_xhigh\")\n",
    "\n",
    "# y boundaries\n",
    "# grid[:, 0, :] = fdtd.PeriodicBoundary(name=\"ybounds\")\n",
    "grid[:, 0:10, :] = fdtd.PML(name=\"pml_ylow\")\n",
    "grid[:, -10:, :] = fdtd.PML(name=\"pml_yhigh\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Grid summary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A simple summary of the grid can be shown by printing out the grid:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(grid)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Running a simulation\n",
    "Running a simulation is as simple as using the `grid.run` method.\n",
    "```python\n",
    "grid.run(\n",
    "    total_time: Number,\n",
    "    progress_bar: bool = True\n",
    ")\n",
    "```\n",
    "Just like for the the lengths in the grid, the `total_time` of the simulation can be specified as an integer (number of `time_steps`) or as a float (in seconds)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid.run(total_time=100)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Grid visualization"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's visualize the grid. This can be done with the `grid.visualize` method:\n",
    "\n",
    "```python\n",
    "# signature\n",
    "grid.visualize(\n",
    "    grid,\n",
    "    x=None,\n",
    "    y=None,\n",
    "    z=None,\n",
    "    cmap=\"Blues\",\n",
    "    pbcolor=\"C3\",\n",
    "    pmlcolor=(0, 0, 0, 0.1),\n",
    "    objcolor=(1, 0, 0, 0.1),\n",
    "    srccolor=\"C0\",\n",
    "    detcolor=\"C2\",\n",
    "    show=True,\n",
    ")\n",
    "```\n",
    "This method will by default visualize all objects in the grid, as well as the current field intensity at a certain `x`, `y` **OR** `z`-plane. By setting `show=False`, one can disable the immediate visualization of matplotlib."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid.visualize(z=0, show=False)\n",
    "import matplotlib.pyplot as plt"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
